﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using CommonClasses;
using Descriptions_DomainModel.Model;
using Descriptions_PinsDescription.Model;
using Eco.ObjectRepresentation;
using Eco.Services;
using CommonClasses.Utility;
using System.Xml.Serialization;
using System.IO;
using CommonClasses.Model;

namespace CommonCycleRobotSupervisor
{
    /// <summary>
    /// Управляющая сборка, осуществляющая работу с MP11 по виртуальным пинам
    /// </summary>
    public class CommonCycleRobotSupervisor : IConnection
    {
        #region Private fields

        /// <summary>
        /// Диспетчер соединений
        /// </summary>
        private IConnection connectionDispatcher;

        /// <summary>
        /// Список пинов состояний
        /// </summary>
        private List<StatePinDefinition> statePins;

        /// <summary>
        /// Список описаний обработки сигналов с пинов датчиков
        /// </summary>
        private List<StatePinHandlerLogic> inPinLogic;

        /// <summary>
        /// Пины на выход
        /// </summary>
        private IList<int> outPins;

        /// <summary>
        /// Провайдер сервисов ECO
        /// </summary>
        private IEcoServiceProvider serviceProvider;
        
        #endregion

        string name;

        /// <summary>
        /// Создать экземпляр и бла-бла-бла...
        /// </summary>
        /// <param name="serviceProvider">Провайдер сервисов ECO</param>
        /// <param name="nodeGroup">Список узлов, которые будут управляться данным Супервизором</param>
        /// <param name="connectionDispatcher">Диспетчер подключений</param>
        /// Путь до файла с описанием логики обработки сигналов с датчиков.
        /// <remarks>
        /// Строка, описывающая логику должна иметь формат:
        /// ((IncrementPin'-'DecrementPin';'StatePinNumber)('|'IncrementPin'-'DecrementPin';'StatePinNumber)*)
        /// <see cref="ANTLR ;)"/>
        /// IncrementPin - номер пина с датчика, по приходу на который 1 значение StatePinNumber увелечивается на 1
        /// DecrementPin - номер пина с датчика, по приходу на который 1 значение StatePinNumber уменьшается на 1
        /// </remarks>
        /// </param>
        public CommonCycleRobotSupervisor(IEcoServiceProvider serviceProvider, NodeGroup nodeGroup, IConnection connectionDispatcher)
        {
            name = Path.GetFileNameWithoutExtension(nodeGroup.AssemblyConfigFileName);

            if (connectionDispatcher == null)
                throw new NullReferenceException("ConnectionDispatcher cannot be null!");

            this.serviceProvider = serviceProvider;
            this.connectionDispatcher = connectionDispatcher;

            FillStatePins(nodeGroup.Nodes);
            
            XmlSerializer xmlser = new XmlSerializer(typeof(string));
            TextReader reader = new StreamReader(nodeGroup.AssemblyConfigFileName);
            string inPinsLogicString = xmlser.Deserialize(reader) as string;
            Console.WriteLine(inPinsLogicString);
            reader.Close();

            FillInPinsLogics(inPinsLogicString);
            FillOutPins(nodeGroup.Nodes);
            SubscribeToConnections();

            Console.WriteLine("CommonCycleRobotSupervisor created");
        }


        #region Configuring

        /// <summary>
        /// Создать список пинов состояний 
        /// </summary>
        /// <param name="nodes">список узлов, у которых есть пины состояний</param>
        private void FillStatePins(IList<Node> nodes)
        {
            statePins = new List<StatePinDefinition>();

            foreach (var node in nodes)
                node.DeepWalk("Nodes", (nod) =>
                        statePins.Add(new StatePinDefinition( ((Node)nod).StatePinNumber,
                            ((Node)nod).StateSet.BottomBound, ((Node)nod).StateSet.UpperBound,0
                        )));
        }

        /// <summary>
        /// Составить список логик обработки входных пинов
        /// </summary>
        /// <param name="logic">логика</param>
        private void FillInPinsLogics(string logic)
        {
            inPinLogic = new List<StatePinHandlerLogic>();
            if (logic != "")
            try
            {
                var logics = logic.Split('|');
                foreach (var log in logics)
                {
                    var parsed = log.Split(';', '-');
                    var statePin = int.Parse(parsed[2]);
                    var incPin = int.Parse(parsed[0]);
                    var decPin = int.Parse(parsed[1]);
                    inPinLogic.Add(new StatePinHandlerLogic
                    (
                        statePin,
                        incPin,
                        1
                    ));
                    inPinLogic.Add(new StatePinHandlerLogic
                    (
                        statePin,
                        decPin,
                        -1
                    ));
                }
            }
            catch (Exception ex)
            {
                throw new FormatException("In pins handling logic definition string has invalid format!", ex);
            }
        }

        /// <summary>
        /// Составить список выходных пинов (методов)
        /// </summary>
        /// <param name="nodes">список узлов, у которых есть методы</param>
        private void FillOutPins(IList<Node> nodes)
        {
            outPins = new List<int>();

            foreach (var node in nodes)
                node.DeepWalk("Nodes", (nod) =>
                    ((Node)nod).Methods.ForEach(method => outPins.Add(method.VirtualPinOut)));
        }

        /// <summary>
        /// Подписаться на изменения пина у соединений
        /// </summary>
        private void SubscribeToConnections()
        {
            connectionDispatcher.OnPinStateChanged += new PinStateChangedHandler(OnPinStateChangedHandler);
        } 

        /// <summary>
        /// Снять подписку на изменение пина у соединения
        /// </summary>
        private void DetachSubscription()
        {
            connectionDispatcher.OnPinStateChanged -= new PinStateChangedHandler(OnPinStateChangedHandler);
        }

        #endregion


        #region IConnection Members

        /// <summary>
        /// Событие: изменился входной пин
        /// </summary>
        public event PinStateChangedHandler OnPinStateChanged;

        public void OnPin(int virtualPinOut, int signal)
        {
            if (outPins.Contains(virtualPinOut))
            {
                Console.WriteLine("{0}.OnPin({1},{2})", name,virtualPinOut,signal);
                //MP9 управляется только логическими сигналами
                if (!((signal == 0) || (signal == 1)))
                    return;
                connectionDispatcher.OnPin(virtualPinOut, signal);
            }
            //else Console.WriteLine("{0}:!(outPins.Contains(virtualPinOut)), {1}", name, virtualPinOut.ToString());
        }

        public void FlushPins()
        {
            Console.WriteLine("CommonCycleRobotSupervisor.FlushPins ");
        }

        public void Dispose()
        {
            Console.WriteLine("CommonCycleRobotSupervisor.Dispose ");
            DetachSubscription();
        }

        #endregion

        /// <summary>
        /// Обработка сигналов с датчиков (обрабатываются только еденицы)
        /// </summary>
        /// <param name="virtualPin">изменившийся вирт. пин</param>
        /// <param name="newState">значение на пине</param>
        private void OnPinStateChangedHandler(int virtualPin, int newState)
        {
            //нас интересуют только поднятые сигналы
            //Console.WriteLine("<{0}.OnPinStateChanged>: {1}:{2}", name, virtualPin, newState);
            if (newState != 1)
                return;
            //ищем логику для данного пина
            var logic = inPinLogic.Find(log => log.PinNumber == virtualPin);
            if (logic == null)
            {
                //Console.WriteLine("<{0}.OnPinStateChanged>: no logic!", name);
                return;
            }
            //ищем описание пина состояния
            var statePin = statePins.Find(spin => spin.PinNumber == logic.StatePin);
            //применяем логику
            statePin.Value = Math.Max(statePin.Min, Math.Min(statePin.Value + logic.Delta, statePin.Max));
            Console.WriteLine("<{0}.OnPinStateChanged>:  ({1},{2})", name, statePin.PinNumber, statePin.Value);
            if (OnPinStateChanged != null)
                OnPinStateChanged(logic.StatePin, statePin.Value);
            //else нас отписали на событие, т.к. реальное соединение не используется
        }
    }
}
 